package com.sevntu.apvs.dModel;

import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.sevntu.apvs.duelement.Element;
import com.sevntu.apvs.duelement.FindCreateElement;

/**
 * Class loads XML config file, contained element and circuit description, and creates a model of discrete unit.
 * 
 * @author <a href="mailto:rd.ryly@gmail.com">Ruslan Diachenko</a>
 */
public class CircuitLoader extends DefaultHandler {

	/** Use for log message generation */
	private static final Logger LOG = Logger.getLogger(CircuitLoader.class.getName());

	/** List of circuit elements */
	private List<Element> elemList;

	/** List of circuits */
	private List<CircuitStructure> circuitList;

	/** Values from XML file to set elements and circuits */
	private String value;

	/** Element counter */
	private int elemCount;

	/** Circuit counter */
	private int circuitCount;

	/** Set of circuit IDs */
	Set<Integer> circuitIDs;

	/** Object contains element validation */
	private FindCreateElement elemValid;

	/** Object describes element structure */
	private Element element;

	/**
	 * Gets number of scheme entries.
	 * 
	 * @return Number of scheme entries.
	 * @throws DModelException
	 *             If there are no any entries in the scheme.
	 */
	public int getSchemeEntryNum() throws DModelException {
		if (circuitIDs.size() == 0) {
			throw new DModelException("There are no any entries in the scheme!!");
		}
		return circuitIDs.size();
	}

	/**
	 * Gets discrete unit model.
	 * 
	 * @param fileName
	 *            XML file name which contains discrete unit description.
	 * @return Discrete unit model.
	 * @throws SAXException
	 *             DModel exception if elements or circuits didn't pass validation.
	 */
	public DModel getDModel(String fileName) throws DModelException {
		elemCount = -1;
		circuitCount = -1;
		circuitIDs = new HashSet<Integer>();

		elemList = new LinkedList<Element>();
		circuitList = new LinkedList<CircuitStructure>();
		elemValid = new FindCreateElement();

		// Get a factory.
		SAXParserFactory saxParsFactory = SAXParserFactory.newInstance();
		try {
			// Get a new instance of parser.
			SAXParser saxParser = saxParsFactory.newSAXParser();

			// Parse the file and also register this class for call backs.
			saxParser.parse(fileName, this);
		} catch (SAXException e) {
			if (e instanceof DModelException) {
				throw (DModelException) e;
			}
			LOG.error(e);
		} catch (ParserConfigurationException e) {
			LOG.error(e);
		} catch (IOException e) {
			LOG.error(e);
		}
		LOG.info("XML file was successfuly read.");

		// Circuit validation.
		CircuitValidation.validate(elemList, circuitList);
		LOG.info("Circuit validation was successfuly passed.");

		return new DModel(elemList, circuitList);
	}

	/* 
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		value = "";
		if ("circuit".equals(qName)) {
			//create a new circuit and add it to circuit list
			circuitList.add(new CircuitStructure());
			circuitCount++;
		}
	}

	/* 
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
	 */
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		value = new String(ch, start, length);
	}

	/* 
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {

		if (value.length() == 0) {
			throw new DModelException("Error in input data. Wrong parameter value for input scheme!!");
		}
		if ("elemName".equals(qName)) {
			checkElement(value);
			element.name = value;
			elemList.add(element);
			elemCount++;
		} else if ("elemId".equals(qName)) {
			elemList.get(elemCount).id = Integer.parseInt(value);
			LOG.debug("Element #" + elemCount + " with ID = " + value);
		} else if ("circuitId".equals(qName)) {
			circuitList.get(circuitCount).setCircuitId(Integer.parseInt(value));
		} else if ("elementNumOut".equals(qName)) {
			circuitList.get(circuitCount).setElemNumOut(Integer.parseInt(value));
			if (Integer.parseInt(value) == 0) {
				circuitIDs.add(circuitList.get(circuitCount).getCircuitId());
			}
		} else if ("contactOut".equals(qName)) {
			circuitList.get(circuitCount).setElemContactOut(Integer.parseInt(value));
		} else if ("elementNumIn".equals(qName)) {
			circuitList.get(circuitCount).setElemNumIn(Integer.parseInt(value));
		} else if ("contactIn".equals(qName)) {
			circuitList.get(circuitCount).setElemContactIn(Integer.parseInt(value));
		}
	}

	/**
	 * Checks element existence in the library.
	 * 
	 * @param elemName
	 *            Element name.
	 * @throws DModelException
	 *             If there is no such element in the library.
	 */
	private void checkElement(String elemName) throws DModelException {
		try {
			element = elemValid.FindElement(elemName);
		} catch (Exception e) {
			throw new DModelException("Errors appeared while checking the element '" + elemName
					+ "' existence in the library!!");
		}
		if (null == element) {
			throw new DModelException("There is no element with such name '" + elemName + "' in the library!!");
		}
	}
}
